package com.acpt.auth.config;

import com.acpt.auth.extension.captcha.CaptchaTokenGranter;
import com.acpt.auth.extension.mobile.SmsCodeTokenGranter;
import com.acpt.auth.extension.refresh.PreAuthenticatedUserDetailsService;
import com.acpt.auth.extension.wechat.WechatTokenGranter;
import com.acpt.auth.userdetails.user.SysUserDetailsServiceImpl;
import com.acpt.common.constant.SecurityConstants;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

@Configuration
@RequiredArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig  extends AuthorizationServerConfigurerAdapter {

  private final AuthenticationManager authenticationManager;
  private final SysUserDetailsServiceImpl sysUserDetailsService;
  private final StringRedisTemplate stringRedisTemplate;
  private final DataSource dataSource;

  private final TokenEnhancer tokenEnhancer;


  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    super.configure(security);
  }

  /**
   * OAuth2客户端
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(jdbcClientDetailsService());
  }

  /**
   * 配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // Token增强
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
    tokenEnhancers.add(tokenEnhancer);
    tokenEnhancers.add(jwtAccessTokenConverter());
    tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

    //token存储模式设定 默认为InMemoryTokenStore模式存储到内存中
    endpoints.tokenStore(jwtTokenStore());

    // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
    List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

    // 添加验证码授权模式授权者
    granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
        endpoints.getOAuth2RequestFactory(), authenticationManager, stringRedisTemplate
    ));

    // 添加手机短信验证码授权模式的授权者
    granterList.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
        endpoints.getOAuth2RequestFactory(), authenticationManager
    ));

    // 添加微信授权模式的授权者
    granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
        endpoints.getOAuth2RequestFactory(), authenticationManager
    ));

    CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
    endpoints
        .authenticationManager(authenticationManager)
        .accessTokenConverter(jwtAccessTokenConverter())
        .tokenEnhancer(tokenEnhancerChain)
        .tokenGranter(compositeTokenGranter)
        .tokenServices(tokenServices(endpoints));
  }

  /**
   * jwt token存储模式
   */
  @Bean
  public JwtTokenStore jwtTokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter());
  }

  /**
   * 使用非对称加密算法对token签名
   */
  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setKeyPair(keyPair());
    return converter;
  }

  /**
   * 密钥库中获取密钥对(公钥+私钥)
   * 生成命令：
   *  keytool -genkeypair -alias jwt -keyalg RSA -keysize 2048 -validity 3650 -keystore keystore.jks
   *  根据提示输入密钥库密码、别名密码、姓名等信息   确定：y
   */
  @Bean
  public KeyPair keyPair() {
    KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "123456".toCharArray());
    KeyPair keyPair = factory.getKeyPair("jwt", "123456".toCharArray());
    return keyPair;
  }


  public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
    tokenEnhancers.add(tokenEnhancer);
    tokenEnhancers.add(jwtAccessTokenConverter());
    tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

    DefaultTokenServices tokenServices = new DefaultTokenServices();
    tokenServices.setTokenStore(endpoints.getTokenStore());
    tokenServices.setSupportRefreshToken(true);
    tokenServices.setClientDetailsService(jdbcClientDetailsService());
    tokenServices.setTokenEnhancer(tokenEnhancerChain);

    // 多用户体系下，刷新token再次认证客户端ID和 UserDetailService 的映射Map
    Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
    clientUserDetailsServiceMap.put(SecurityConstants.ADMIN_CLIENT_ID, sysUserDetailsService); // 系统管理客户端
//    clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID, memberUserDetailsService); // Android、IOS、H5 移动客户端
//    clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID, memberUserDetailsService); // 微信小程序客户端

    // 刷新token模式下，重写预认证提供者替换其AuthenticationManager，可自定义根据客户端ID和认证方式区分用户体系获取认证用户信息
    PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
    provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));
    tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));

    /**
     * refresh_token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
     *  1 重复使用：access_token过期刷新时， refresh_token过期时间未改变，仍以初次生成的时间为准
     *  2 非重复使用：access_token过期刷新时， refresh_token过期时间延续，在refresh_token有效期内刷新便永不失效达到无需再次登录的目的
     */
    tokenServices.setReuseRefreshToken(true);
    return tokenServices;

  }


  @Bean
  public JdbcClientDetailsService jdbcClientDetailsService() {
    return new JdbcClientDetailsService(dataSource);
  }
}
